﻿using System;
using System.Linq;
using System.Windows;
using Bettzueche.RLLibrary.Types;
using Bettzueche.RLLibrary.Component;
using Bettzueche.RLLibrary.Functions;
using System.Collections.Generic;

namespace RLProject1.Model.AMEEWorld
{
    /// <summary>
    /// Umwelt.<para>
    /// Zweites Beispiel, dass SarasaLambda mit Funktionasapproximation zeigen soll. Es ist ausserdem angelehnt an den künftigen Einsatz
    /// in der AMEE-Software.
    /// </para><para>
    /// Die 'Real'-Welt ist durch eine Funktion simuliert, die zu jeder reelen x,y Koordinate ein Maß der Unebenheit wiedergibt (i.d.R. ein Wert von 0.0 bis 1.0).
    /// Die innere Klasse WorldImage ist die Welt aus Agentensicht, die eigentliche IEnvironment. Sie beinhaltet eine egozentrische Sicht (quasi ein onboard Radar) mit
    /// in Sektoren und Spuren unterteilte Abschnitte, die ein durchschnittliches Unebenheitsmaß der 'Real'-Welt widergeben.
    /// </para><para>
    /// Man beachte, dass die Agentenwelt zwar eine feste Anzahl Abschnitte hat (diskrete Welt) aber veränderlich und somit kontinuierlich ist. Mit jedem Schritt wird ein
    /// neues Abbild mit aktualisierten Durchschnittswerten erzeugt. Der (Welt-)Zustand ist somit ein kontinuierliche reeler Unebenheits-Wert für jeden Abschnitt.</para>
    /// </summary>
    public class AMEEWorldDraft : IEnvironment<WorldImage, AMEEAction, double>
    {


        #region private Felder

        /// <summary>Startkoordinate</summary>
        Vector _start;
        /// <summary>Position in der 'realen' Welt</summary>
        Vector _realKoordinate;
        WorldImage _agentenWeltZustand;
        const float _defaultSpurBreite = 0.5f;
        static Tiling _defaultTiling = new Tiling(10, 8, new float[] { _defaultSpurBreite, 2 * _defaultSpurBreite });
        /// <summary>Minimale Abstand zum Ziel, der als 'Treffer' gewertet wird. (typischerw. Hälfte erste Spurbreite)</summary>
        double _minimumAbstandZumZiel;
        

        #endregion


        #region Poperties

        /// <summary>
        /// Gets or sets the Zielkoordinate, bei der es ein Belohnung gibt.
        /// </summary>
        /// <value>
        /// The ziel.
        /// </value>
        public Vector Ziel { get; set; }

        /// <summary>
        /// Gets or sets the Funktion, die die Realwelt in Form von Unebenheitswerten widergibt.
        /// </summary>
        /// <value>
        /// The realwelt unebenheits funktion.
        /// </value>
        public BumpinessFunction RealweltUnebenheitsFunktion { get; set; }

        #endregion



        #region Konstruktor(en)
        /// <summary>
        /// Initialisiert Standardvariante von <see cref="AMEEWorldDraft"/>.<para>
        /// Weltfunktion = <see cref="Bettzueche.RLLibrary.Functions.HilfsFunktionen.Weltfunktion">HilfsFunktionen.Weltfunktion</see></para><para>
        /// Start = (0.0 , 0.0)</para>
        /// </summary>
        internal AMEEWorldDraft() : this(HilfsFunktionen.Weltfunktion, .0,.0)
        {
        }

        /// <summary>
        /// Initialisiert Standardvariante von <see cref="AMEEWorldDraft"/>.<para>
        /// Weltfunktion = <see cref="Bettzueche.RLLibrary.Functions.HilfsFunktionen.Weltfunktion">HilfsFunktionen.Weltfunktion</see></para>
        /// </summary>
        /// <param name="startX">Startposition X</param>
        /// <param name="startY">Startposition Y</param>
        internal AMEEWorldDraft(double startX, double startY)
            : this(HilfsFunktionen.Weltfunktion, startX, startY)
        {
        }

        /// <summary>
        /// Initialisiert neu Instanz der <see cref="AMEEWorldDraft"/> Klasse.
        /// </summary>
        /// <param name="realWelt">Abbildungs der Realwelt von (x,y)-Koordinate auf Unebenheitsmaß.</param>
        /// <param name="startX">Startposition X</param>
        /// <param name="startY">Startposition Y</param>
        internal AMEEWorldDraft(BumpinessFunction realWelt, double startX, double startY)
            : this(realWelt, startX, startY, AMEEWorldDraft._defaultTiling)
        {
        }

        /// <summary>
        /// Initialisiert neu Instanz der <see cref="AMEEWorldDraft"/> Klasse.
        /// </summary>
        /// <param name="realWelt">Abbildungs der Realwelt von (x,y)-Koordinate auf Unebenheitsmaß.</param>
        /// <param name="startX">Startposition X</param>
        /// <param name="startY">Startposition Y</param>
        /// <param name="tiling">Konfiguration / Diskretisierung der Zustände</param>
        public AMEEWorldDraft(BumpinessFunction realWelt, double startX, double startY, Tiling tiling) {
            // Defaultwerte:
            int messReihen = 3;
            int messWinkel = 5;

            _start = new Vector { X = startX, Y = startY }; ;
            _realKoordinate = _start;
            this.RealweltUnebenheitsFunktion = realWelt;
            this._agentenWeltZustand = new WorldImage(tiling, messReihen, messWinkel, realWelt);
            _minimumAbstandZumZiel = tiling.SpurRadien[0] / 2.0;
            this.Init(null);
        }
        #endregion


        #region IEnvironment Member

        /// <summary>
        /// Gibt eine Kopie des aktuellen (Welt-)zustands.
        /// </summary>
        public WorldImage State
        {
            get { return this._agentenWeltZustand.Clone(); }
        }
        /// <summary>
        /// Gibt an, ob der aktuelle Zustand ein Endzustand ist.
        /// </summary>
        public bool IsFinal {
            get {
                Vector abstand = Ziel - this._realKoordinate;
                return Math.Abs(abstand.Length) < _minimumAbstandZumZiel;
            }
        }


        /// <summary>
        /// Angegebene Aktion ausführen.
        /// </summary>
        /// <param name="a">auszuführende Aktion</param>
        /// <param name="nextState">Folgezustand (wird gesetzt; siehe auch Prop State)</param>
        /// <returns>Belohnung r (Reward)</returns>
        public double DoAction(AMEEAction a, out WorldImage nextState) {
            Vector altePos = _realKoordinate;
            // Setze aktuelle Realweltpostion auf RealX + a.X und RealY + a.Y (Real-Koord nur für simulation der Realwelt, die Agentenwelt ist das WorldImage!)
            _realKoordinate.X += a.X;
            _realKoordinate.Y += a.Y;

            // Ermittle (Messe) des WeltAbbild, setze neue Zielrichtung und gib das als Folgezustand
            this._agentenWeltZustand.Messe(_realKoordinate.X, _realKoordinate.Y);
            Vector vAgentZiel = this.Ziel - _realKoordinate;
            this._agentenWeltZustand.SetZielrichtung(vAgentZiel);
            nextState = this.State;

            Vector s_vec = altePos - _realKoordinate;
            double s = Math.Abs(s_vec.Length);
            // Unebenheit am Ziel:
            double u = this.RealweltUnebenheitsFunktion(_realKoordinate.X, _realKoordinate.Y);
            // Bestrafung / Belohnung (Vergeltung):
            double r = 0.0;
            if (this.IsFinal) {
                r = 1000.0;
            }
            else
                r = rFunc.Eval(u, s);
            return r;
        }
        Vergeltungssunktion rFunc = new Vergeltungssunktion { Eval = (u, s) => -(1 + u) * s }; // SUPER schlechtes Ergerbnis im Test
            
        //public double DoAction(AMEEAction a, out WorldImage nextState)
        //{
        //    Vector altePos = _realKoordinate;
        //    // Setze aktuelle Realweltpostion auf RealX + a.X und RealY + a.Y (Real-Koord nur für simulation der Realwelt, die Agentenwelt ist das WorldImage!)
        //    _realKoordinate.X += a.X;
        //    _realKoordinate.Y += a.Y;

        //    // Ermittle (Messe) des WeltAbbild, setze neue Zielrichtung und gib das als Folgezustand
        //    this._agentenWeltZustand.Messe(_realKoordinate.X, _realKoordinate.Y);
        //    Vector vAgentZiel = this.Ziel - _realKoordinate;
        //    this._agentenWeltZustand.SetZielrichtung(vAgentZiel);
        //    nextState = this.State;

        //    // Ermittle (irgendwie) Belohnung/Bestrafung für die Rückgabe
        //    //Euklidische Distanz der zurückgelegten Strecke in Richtung Ziel
        //    Vector eZielVec = (this.Ziel - altePos); // ursprüngliche Zielrichtung
        //    eZielVec.Normalize();
        //    double s = (_realKoordinate - altePos) * eZielVec; //* eZielVec = WegVektor
        //    // Unebenheit am Ziel:
        //    double u = this.RealweltUnebenheitsFunktion(_realKoordinate.X, _realKoordinate.Y);
        //    // Bestrafung / Belohnung (Vergeltung):
        //    double r = 0.0;
        //    if (this.IsFinal) {
        //        r = 1000.0;
        //    }
        //    else 
        //        r = Vergeltungssunktion.StandardVergeltung.Eval(u, s);
        //    return r;
        //}

        /// <summary>
        /// Setzt den Zustand auf Startposition.<para><para>
        /// Kann somit auch als reset nach einem Episodendurlauf genutzt werden.</para>
        /// (Geerbt von IEnvironment)</para>
        /// </summary>
        /// <param name="specifications">Wird ignoriert!</param>
        public void Init(string specifications)
        {
            // Position auf Start, Zustandswerte setzen
            _realKoordinate = _start;
            this._agentenWeltZustand.Messe(_realKoordinate.X, _realKoordinate.Y);
            Vector vAgentZiel = this.Ziel - _realKoordinate;
            this._agentenWeltZustand.SetZielrichtung(vAgentZiel);
        }

        /// <summary>
        /// Does the action.<para>
        /// (Geerbt von IEnvironment)</para>
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="nextState">next State.</param>
        /// <returns>
        /// Reward for performing the given action
        /// </returns>
        /// <exception cref="System.NotSupportedException">wenn action nicht vom Typ <see cref="AMEEWorldDraft.AMEEAction"/> ist.</exception>
        public double DoAction(IAction action, out IState nextState)
        {
            if (action is AMEEAction)
            {
                WorldImage stat;// = nextState as WorldImage;
                double resu = this.DoAction((AMEEAction)action, out stat);
                nextState = stat;
                return resu;
            }
            else
            {
                throw new NotSupportedException(action.GetType() + " is not supported for action.");
            }
        }

        #endregion
    }
}
